/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// sv_main.cpp -- server main loop


#include "sv_local.h"


serverActive_t	sv;
serverStatic_t	svs;

cvar_t *		sv_serverId;
cvar_t *		sv_protocol;
cvar_t *		sv_hostName;
cvar_t *		sv_map;
cvar_t *		sv_nextMap;
cvar_t *		sv_pure;
cvar_t *		sv_fps;
cvar_t *		sv_numClients;
cvar_t *		sv_maxClients;
cvar_t *		sv_maxRate;
cvar_t *		sv_reconnectLimit;


/*
 ==================
 SV_SendReliableCommand

 The given command will be sent to the client, and is guaranteed to not have
 any future snapshots processed before it is executed
 ==================
*/
void SV_SendReliableCommand (client_t *client, const char *fmt, ...){

	char	command[MAX_STRING_LENGTH];
	va_list	argPtr;
	int		index;
	int		len;
	byte	buf[MAX_MSGLEN];
	ByteBuffer msg;
	msg.Init(buf,MAX_MSGLEN,false);
	if (client->state < CS_CONNECTED)
		return;

	va_start(argPtr, fmt);
	len = Str_VSPrintf(command, sizeof(command), fmt, argPtr);
	va_end(argPtr);

	if (len == -1)
		Com_Error(false, "SV_SendReliableCommand: command string overflow");

	msg.WriteByte(SVC_COMMAND);
	msg.WriteData(command,len);
	ENetPacket *packet=enet_packet_create(msg.data,msg.getBytesWritten(),ENET_PACKET_FLAG_RELIABLE);
	enet_peer_send(client->peer,0,packet);
	enet_packet_destroy(packet);
}

/*
 ==================
 SV_BroadcastReliableCommand

 The given command will be broadcast to all clients, and is guaranteed to not
 have any future snapshots processed before it is executed
 ==================
*/
void SV_BroadcastReliableCommand (const char *fmt, ...){

	client_t	*client;
	char		command[MAX_STRING_LENGTH];
	va_list		argPtr;
	int			index;
	int			i, len;

	va_start(argPtr, fmt);
	len = Str_VSPrintf(command, sizeof(command), fmt, argPtr);
	va_end(argPtr);

	if (len == -1)
		Com_Error(false, "SV_BroadcastReliableCommand: command string overflow");

	// Echo broadcast prints to the dedicated console
	if (com_dedicated->integerValue && !Str_ICompareChars(command, "print ", 6)){
		Com_Printf("Broadcast: %s", command + 6);

		if (command[len-1] != '\n')
			Com_Printf("\n");
	}

	// Send the command to all relevant clients
	for (i = 0, client = svs.clients; i < sv_maxClients->integerValue; i++, client++){
		if (client->state < CS_PRIMED)
			continue;
		SV_SendReliableCommand(client,command);
	}
}

const char *NET_NetAdrToString(ENetPeer *peer){
	static char name[64];
	::enet_address_get_host(&peer->address,name,64);
	return name;
}
/*
 ==================
 SV_ConnectPacket

 Connects the client to the server
 ==================
*/
void SV_OnClientConnected(ENetPeer *from){

	client_t	*client;
	char		userInfo[MAX_STRING_LENGTH];
	const char	*infoString, *reason;
	char		*ch;
	int			protocol, clientPort, challenge;
	int			ping;
	int			i;

	// Otherwise find a free client slot
	for (i = 0, client = svs.clients; i < sv_maxClients->integerValue; i++, client++){
		if (client->state == CS_FREE)
			break;
	}

	if (i == sv_maxClients->integerValue){
		Com_DPrintf("Rejected client connection from %s: no free client slots\n", NET_NetAdrToString(from));

		////NET_OutOfBandPrint(NS_SERVER, from, "connectResponse Server is full");
		return;
	}

	// Accept the new client.
	// This is the only place a client_t is ever initialized.
	memset(client, 0, sizeof(client_t));

	// Give the game a chance to reject the connection or modify the user info
	reason = sg->ClientConnect(client - svs.clients, true, NET_NetAdrToString(from), userInfo);
	if (reason){
		Com_DPrintf("Rejected client connection from %s: rejected by game (%s)\n", NET_NetAdrToString(from), reason);

		//NET_OutOfBandPrint(NS_SERVER, from, "connectResponse %s", reason);
		return;
	}

	// Update number of clients for server info
	CVar_SetInteger(sv_numClients, sv_numClients->integerValue + 1);

	// Set up the new client
	client->clientNum = client - svs.clients;

	Str_Copy(client->userInfo, userInfo, sizeof(client->userInfo));

	SV_UserInfoChanged(client);

	// Set up the network channel
	Com_DPrintf("Going to CS_CONNECTED for %s\n", client->name);

	client->state = CS_CONNECTED;
	client->peer=from;
}

/*
 ==================
 SV_CheckServerInfo
 ==================
*/
static void SV_CheckServerInfo (void){

	// Send a reliable server info update if needed
	if (CVar_GetModifiedFlags() & CVAR_SERVERINFO){
		CVar_ClearModifiedFlags(CVAR_SERVERINFO);

		SV_BroadcastReliableCommand("serverInfo \"%s\"", CVar_ServerInfoString());
	}
}

/*
 ==================
 SV_FinalMessage

 Used by SV_Shutdown to send a final message to all connected clients before
 the server goes down. The messages are sent immediately because the server is
 going to totally exit after returning from this function.
 ==================
*/
static void SV_FinalMessage (const char *message){

	client_t	*client;
	int			i, j;

	if (!svs.clients)
		return;

	// Send it twice in case one is dropped
	for (i = 0; i < 2; i++){
		for (j = 0, client = svs.clients; j < sv_maxClients->integerValue; j++, client++){
			if (client->state < CS_CONNECTED)
				continue;
			enet_peer_disconnect(client->peer,0);
		}
	}
}


void SV_ReadClientPackets(){
	ENetEvent event;
    if(!svs.server)
		return;
	ByteBuffer msg;
	byte buf[MAX_MSGLEN];
	msg.Init(buf,MAX_MSGLEN,false);
    /* Wait up to 1000 milliseconds for an event. */
    while (enet_host_service (svs.server, &event, 1) > 0)
    {
		

        switch (event.type)
        {
        case ENET_EVENT_TYPE_CONNECT:
			//generated upon connection success
            SV_OnClientConnected(event.peer);
			//enet_packet_destroy (event.packet);
            break;
        case ENET_EVENT_TYPE_RECEIVE:
			msg.ClearWriteState();
			msg.ClearReadState();
			msg.WriteData(event.packet->data,event.packet->dataLength);
			SV_OnClientPacket(event.peer,&msg);
			enet_packet_destroy (event.packet);
            break;
        case ENET_EVENT_TYPE_DISCONNECT:
			//this gets generated when disconnection is complete
			SV_OnClientDisconnected(event.peer);
            break;
        }
    }
}

// ============================================================================

/*
 ==================
 SV_Frame
 ==================
*/
void SV_Frame (int msec){
	char	map[MAX_OSPATH];
	int		time;

	if (!svs.initialized)
		return;		// Nothing running on the server

	if (com_speeds->integerValue)
		time = Sys_Milliseconds();

	//TODO: Find out why msec is huge if game is started with +devmap test
	sv.timeResidual += (msec>1000)?1000:msec;

	// If running a dedicated server, sleep until either a packet arrives from
	// the network or time enough for a frame has gone by
	if (com_dedicated->integerValue){
		if (sv.timeResidual < sv.frameMsec){
			if (com_speeds->integerValue)
				com_timeWaiting += (sv.frameMsec - sv.timeResidual);

			//NET_WaitForPackets(sv.frameMsec - sv.timeResidual);
			return;
		}
	}

	// If time is about to hit the 32nd bit, kick all clients and clear
	// svs.time, rather than checking for negative time wrap-around everywhere
	if (svs.time > 0x70000000){
		if (sv_nextMap->value[0])
			Str_Copy(map, sv_nextMap->value, sizeof(map));
		else
			Str_Copy(map, sv_map->value, sizeof(map));

		// Kill the server, but also drop the client connection
		SV_Shutdown("Restarting server due to time wrapping");
		CL_Drop("Restarting server due to time wrapping");

		// We can't restart the server immediately because the local client
		// will have disconnect messages in the loopback buffers, so just
		// buffer a map command and execute it a few frames later
		//if (sv_allowCheats->integerValue)
		//	Cmd_AppendText(Str_VarArgs("wait 5; devMap %s\n", map));
		//else
		//	Cmd_AppendText(Str_VarArgs("wait 5; map %s\n", map));

		return;
	}

	// Check if a client connection timed out
	//SV_CheckTimeOuts();

	// Check if we need to send a server info update
	SV_CheckServerInfo();

	// Run the game simulation in chunks
	while (sv.timeResidual >= sv.frameMsec){
		sv.timeResidual -= sv.frameMsec;
		svs.time += sv.frameMsec;

		sg->RunFrame(svs.time);
	}

	//read all client packets
	SV_ReadClientPackets();
	// Send packets back to the clients
	SV_SendClientPackets();

	// Send a heartbeat to the master server if needed
//	SV_MasterHeartbeat();

	if (com_speeds->integerValue)
		com_timeServer += (Sys_Milliseconds() - time);
}

/*
 ==================
 SV_Init

 Called only from Com_Init, not for each game
 ==================
*/
void SV_Init (void){

	// Register our variables and commands
	sv_serverId = CVar_Get("sv_serverId", "0", CVAR_SERVERINFO | CVAR_ROM, "Server identification");
	sv_protocol = CVar_Get("sv_protocol", Str_FromInteger(PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM, "Server protocol version");
	sv_hostName = CVar_Get("sv_hostName", ENGINE_NAME " Server", CVAR_ARCHIVE | CVAR_SERVERINFO, "Server host name");
	sv_map = CVar_Get("sv_map", "", CVAR_SERVERINFO | CVAR_ROM, "Map being played");
	sv_nextMap = CVar_Get("sv_nextMap", "", CVAR_SERVERINFO, "Map to be played next");
	sv_pure = CVar_Get("sv_pure", "1", CVAR_SERVERINFO | CVAR_LATCH, "Pure server");
	sv_fps = CVar_Get("sv_fps", "30", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_LATCH, "Server frames per second");
	sv_numClients = CVar_Get("sv_numClients", "0", CVAR_SERVERINFO | CVAR_ROM, "Number of clients currently on the server");
	sv_maxClients = CVar_Get("sv_maxClients", "32", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_LATCH, "Maximum number of clients allowed on the server");
	sv_maxRate = CVar_Get("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, "Maximum rate allowed for internet clients (0 = unlimited)");
	sv_reconnectLimit = CVar_Get("sv_reconnectLimit", "3.0", 0, "Time, in seconds, before a client is allowed to reconnect");
	
	Cmd_AddCommand("map", SV_Map_f, "Loads a map");
	Cmd_AddCommand("devMap", SV_DevMap_f, "Loads a map in developer mode");
	Cmd_AddCommand("nextMap", SV_NextMap_f, "Loads the next map");
	Cmd_AddCommand("killServer", SV_KillServer_f, "Kills the server");
	Cmd_AddCommand("serverStatus", SV_ServerStatus_f, "Shows server status");
	Cmd_AddCommand("serverInfo", SV_ServerInfo_f, "Shows server information");
	Cmd_AddCommand("dumpUserInfo", SV_DumpUserInfo_f, "Shows user information for a player");
	Cmd_AddCommand("kickClient", SV_KickClient_f, "Kicks a user by client number");
	Cmd_AddCommand("kickPlayer", SV_KickPlayer_f, "Kicks a user by player name");
	Cmd_AddCommand("kickAll", SV_KickAll_f, "Kicks all users");
}

/*
 ==================
 SV_Shutdown

 Called from Com_Shutdown, but also when each game quits or an error is thrown
 ==================
*/
void SV_Shutdown (const char *message){

	client_t	*client;
	int			i;

	if (!svs.initialized)
		return;		// Nothing running on the server

	Com_Printf("------- Server Shutdown -------\n");

	// Send a final message
	SV_FinalMessage(message);

	// Shutdown game module
	SG_Shutdown();

	// Wipe the entire serverActive_t structure
	memset(&sv, 0, sizeof(serverActive_t));

	// Free clients
	if (svs.clients){
		
		Mem_Free(svs.clients);
	}

	// Wipe the entire serverStatic_t structure
	memset(&svs, 0, sizeof(serverStatic_t));

	// Set the server state
	Com_SetServerState(false);

	// Free the clip map
	CM_FreeMap();

	// Allow cheats
	CVar_FixCheatVariables(true);

	// Clear server info variables
	CVar_SetInteger(sv_serverId, 0);
	CVar_SetString(sv_map, "");
	CVar_SetString(sv_nextMap, "");
	CVar_SetInteger(sv_numClients, 0);

	Com_Printf("-------------------------------\n");
}
